import VNode from "./vnode"
// 创建空节点
export const emptyNode = new VNode('', {}, [])

/**
 * 判断两个虚拟节点是否相同
 * @param {*} a 虚拟节点 
 * @param {*} b 虚拟节点
 * @returns 
 */
function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}
/**
 * 
 * @param {*} a 
 * @param {*} b 
 * @returns 
 */
function sameInputType (a, b) {
  if (a.tag !== 'input') return true
  let i
  const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
  const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
  return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}
/**
 * 用于将一组子节点的key属性与他们在旧的VNode数组中的索引建立映射关系
 * @param {*} children 要建立映射关系子节点数组
 * @param {*} beginIdx 子节点数组中开始建立映射的位置
 * @param {*} endIdx 子节点数组中建立映射的结束位置
 */
function createKeyToOldIdx (children, beginIdx, endIdx) {
  let i, key
  const map = {}
  // 遍历从beginIdx 到endIdx的子节点数组 取出每个子节点的key 属性 并判断其是否定义
  for (i = beginIdx; i <= endIdx; i++) {
    key = children[i].key
    if (isDef(key)) {
      map[key] = i
    }
  }
  return map
}
export function createPatchFunctionFunction (backend) {
  let i, j
  const cbs = {}
  const { modules, nodeOps } = backend

  for (i = 0; i < hooks.length; ++i) {
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }
  /**
   * 创建真实DOM元素的函数
   * @param {*} vnode  当前要创建元素的虚拟节点
   * @param {*} insertedVnodeQueue  已插入的虚拟节点队列
   * @param {*} parentElm 父级元素 新创建的元素将插入到此父级元素中
   * @param {*} refElm 参考元素 在这个元素之前插入新创建的元素
   * @param {*} nested 是否欠他
   * @param {*} ownerArray 所属数组 
   * @param {*} index 在ownerArray中的索引
   * @returns 
   */
  function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
    if (isDef(vnode.elm) && isDef(ownerArray)) {
      vnode = ownerArray[index] = cloneVNode(vnode)
    }
    vnode.isRootInsert = !nested
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }
    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    if (isDef(tag)) {
      // 如果是标签节点
      vnode.elm = nodeOps.createElement(tag, vnode)
      insert(parentElm, vnode.elm, refElm)
    } else if (isTrue(vnode.isComment)) {
      // 如果是注释节点
      vnode.elm = nodeOps.createComment(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    } else {
      // 如果是文本节点
      vnode.elm = nodeOps.createTextNode(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    }
  }
  /**
   * 根据虚拟节点创建组件,并处理组件的初始化 激活等情况
   * @param {*} vnode 当前要创建组件的虚拟节点
   * @param {*} insertedVnodeQueue 已插入的虚拟节点队列
   * @param {*} parentElm 父级元素 组件将被插入到此父级元素中
   * @param {*} refElm 参考元素 在这个元素之前插入新创建的组件
   * @returns true 说明组件创建过程已经完成
   */
  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data // 获取虚拟节点的data 属性 并判断是否存在 如果不存在则说明该节点不是组件节点 返回false
    if (isDef(i)) {
      // 判断是否已经存在组件实例并且组件配置中的keppAlive属性为true 即组件是一个缓存的激活组件
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        i(vnode, false, parentElm, refElm)
      }
      if (isDef(vnode.componentInstance)) {
        // 判断虚拟节点是否存在组件实例 如果存在 进行组件的初始化 并将组件节点添加到插入队列中
        initComponent(vnode, insertedVnodeQueue)
        if (isTrue(isReactivated)) {
          // 组件是一个缓存的激活组件 需要重新激活函数会调用reactiveComponent 进行激活
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }
  /**
   * 进行组件的初始化,并将组件节点添加到插入队列中
   * @param {} vnode 要初始化的虚拟节点 也就是组件节点
   * @param {*} insertedVnodeQueue 已插入的虚拟节点队列
   */
  function initComponent (vnode, insertedVnodeQueue) {
    // 判断虚拟节点的data.pendingInsert是否存在
    if (isDef(vnode.data.pendingInsert)) {
      // 如果存在则插入队列中
      insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
      vnode.data.pendingInsert = null
    }
    // 获取组件实例根元素
    vnode.elm = vnode.componentInstance.$el
    if (isPatchable(vnode)) {
      // 如果可以patch 则元素类型为元素节点、注释节点或文本节点
      invokeCreateHooks(vnode, insertedVnodeQueue)
      // 电泳invokeCreateHooks 函数触发所有模块的create钩子函数 并执行作用域样式设置
      setScoped(vnode)
    } else {
      //函数节点不可patch 说明根节点是一个空元素 则注册
      registerRef(vnode)
      insertedVnodeQueue.push(vnode)
    }
  }
  /**
   * 组件的重新激活函数,遍历内部节点及其子组件 找到具有过渡效果的节点
   * @param {*} vnode 要重新激活的虚拟节点
   * @param {*} insertedVnodeQueue  已插入的虚拟节点队列
   * @param {*} parentElm 父级元素 组件将被插入到此父级元素中
   * @param {*} refElm 参考元素
   */
  function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i
    let innerNode = vnode
    while (innerNode.componentInstance) {
      innerNode = innerNode.componentInstance._vnode
      // 如果data.transition存在说明该节点具有过渡效果
      if (isDef(i = innerNode.data) && isDef(i = i.transition)) {
        for (i = 0; i < cbs.activete.length; ++i) {
          cbs.activete[i](emptyNode, innerNode)
        }
        // 将内部节点加入插入队列
        insertedVnodeQueue.push(innerNode)
        break
      }
    }
    insert(parentElm, vnode.elm, refElm)
  }
  /**
   * 插入函数
   * @param {*} parent 要插入元素的父级元素 
   * @param {*} elm 要插入的元素
   * @param {*} ref 参考元素 在这个元素怒之前插入
   */
  function insert (parent, elm, ref) {
    // 检查parent是否存在
    if (isDef(parent)) {
      // 检查ref是否存在
      if (isDef(ref)) {
        // 如果ref存在并且他的父节点是parent 则将要插入的元素插入到ref之前
        if (ref.parentNode === parent) {
          nodeOps.insertBefore(parent, elm, ref)
        }
      } else {
        // 否则将要插入的元素添加到parent 的子节点列表末尾
        nodeOps.appendChild(parent, elm)
      }
    }
  }
  /**
   * 根据传入的 children 创建对应的真实 DOM 子节点，并将其插入到父节点 vnode.elm 中
   * @param {*} vnode 
   * @param {*} children 
   * @param {*} insertedVnodeQueue 虚拟节点队列
   */
  function createChildren (vnode, children, insertedVnodeQueue) {
    if (Array.isArray(children)) {
      for (let i = 0; i < children.length; i++) {
        createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
      }
    } else if (isPrimitive(vnode.text)) {
      nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
    }
  }
  function isPatchable (vnode) {
    while (vnode.componentInstance) {
      vnode = vnode.componentInstance._vnode
    }
    return isDef(vnode.tag)
  }
  function invokeCreateHooks (vnode, insertedVnodeQueue) {
    for (let i = 0; i < cbs.create.length; ++i) {
      cbs.create[i](emptyNode, vnode)
    }
    i = vnode.data.hook
    if (isDef(i)) {
      if (isDef(i.create)) i.create(emptyNode, vnode)
      if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
    }
  }
  /**
   * 设置节点的作用域
   * @param {*} vnode 
   */
  function setScope (vnode) {
    let i
    //isDef判断是否存在xx属性
    if (isDef(i = vnode.fsScopeId)) {
      nodeOps.setStyleScope(vnode.elm, i)
    } else {
      let ancestor = vnode
      while (ancestor) {
        if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
          nodeOps.setStyleScope(vnode.elm, i)
        }
        ancestor = ancestor.parent
      }
    }
    if (isDef(i = activeInstance)
      && i !== vnode.context &&
      i !== vnode.fnContext &&
      isDef(i = i.$options._scopeId)) {
      nodeOps.setStyleScope(vnode.elm, i)
    }
  }
  /**
   * 向父元素插入一组虚拟节点
   * @param {*} parentElm 父元素
   * @param {*} refElm 草靠元素
   * @param {*} vnodes 创建真实DOM元素的虚拟节点
   * @param {*} startIdx 当前循环迭代的索引值
   * @param {*} endIdx 
   * @param {*} insertedVnodeQueue 存储用于已插入的虚拟节点
   */
  function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {
    for (; startIdx <= endIdx; ++startIdx) {
      createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx)
    }
  }
  function invokeDestroyHook (vnode) {
    let i, j
    const data = vnode.data
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
      for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
    }
    if (isDef(i = vnode.children)) {
      for (j = 0; j < vnode.children.length; ++j) {
        invokeDestroyHook(vnode.children[j])
      }
    }
  }
  /**
   * 删除节点
   * @param {*} parentElm 
   * @param {*} vnodes 
   * @param {*} startIdx 
   * @param {*} endIdx 
   */
  function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
    for (; startIdx <= endIdx; ++startIdx) {
      const ch = vnodes[startIdx]
      if (isDef(ch)) {
        if (isDef(ch.tag)) {
          removeAndInvokeRemoveHook(ch)
          invokeDestroyHook(ch)
        } else { // Text node
          removeNode(ch.elm)
        }
      }
    }
  }

  function removeAndInvokeRemoveHook (vnode, rm) {
    if (isDef(rm) || isDef(vnode.data)) {
      let i
      const listeners = cbs.remove.length + 1
      if (isDef(rm)) {
        // we have a recursively passed down rm callback
        // increase the listeners count
        rm.listeners += listeners
      } else {
        // directly removing
        rm = createRmCb(vnode.elm, listeners)
      }
      // recursively invoke hooks on child component root node
      if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {
        removeAndInvokeRemoveHook(i, rm)
      }
      for (i = 0; i < cbs.remove.length; ++i) {
        cbs.remove[i](vnode, rm)
      }
      if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
        i(vnode, rm)
      } else {
        rm()
      }
    } else {
      removeNode(vnode.elm)
    }
  }
  /**
   * 
   * @param {*} parentElm 父元素的DOM节点
   * @param {*} oldCh 旧子节点列表
   * @param {*} newCh 新子节点列表
   * @param {*} insertedVnodeQueue 
   * @param {*} removeOnly 
   */
  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0  //旧子节点列表的起始索引
    let newStartIdx = 0  //新子节点列表起始索引
    let oldEndIdx = oldCh.length - 1 //旧子节点列表的结束索引
    let oldStartVnode = oldCh[0] // 旧子节点列表的第一个虚拟节点
    let oldEndVnode = oldCh[oldEndIdx] // 旧子节点列表的结束索引
    let newEndIdx = newCh.length - 1 // 新子节点列表的结束索引
    let newStartVnode = newCh[0] // 新子节点列表的第一个虚拟节点
    let newEndVnode = newCh[newEndIdx] // 新子节点列表的最后一个虚拟节点
    // oldKeyToIdx:Object  用于存储旧子节点列表中带有key的虚拟节点的索引值
    // idxInOld: 存储新子节点列表中的虚拟节点再旧子节点列表中的索引值
    // vnodeToMove 表示需要移动的虚拟节点
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm
    // 如果先遍历完旧节点列表或者新子节点列表先遍历完结束循环
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        // 如果旧子节点列表的起始节点不存在
        oldStartVnode = oldCh[++oldStartIdx] // 则将结束索引向向后移动一位
      } else if (isUndef(oldEndVnode)) { // 旧节点列表的结束节点不存在
        oldEndVnode = oldCh[--oldEndIdx] //将旧子节点的结束索引向前移动一位
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // 首首比较 oldCh 如果旧子节点的起始节点与 新子节点列表的起始节点是相同的 则使用patchVnode更新这两个节点
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        oldStartVnode = oldCh[++oldStartIdx] // 将旧子节点列表的起始索引向后移动一位
        newStartVnode = newCh[++newStartIdx] // 将新子节点列表的起始索引向后移动一位
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 尾尾比较 如果旧子节点列表的结束节点与新子节点列表的结束节点相同
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) // 更新两个节点
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        // 首尾比较
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx] // 将旧子节点列表的开始节点后移一位
        newEndVnode = newCh[--newEndIdx] // 将新子节点列表的结束节点前移一位
      } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // 尾首比较
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx] // 将旧子节点的结束节点后移一位
        newStartVnode = newCh[++newStartIdx] // 将新子节点列表的开始节点前移一位
      } else {
        // 在vnode 不带key的情况每一轮的diff过程当中都是新旧节点的起始和结束节点两两进行比较,知道新节点列表或者旧节点列表遍历完
        if (isUndef(oldKeyToIdx)) {
          // 当发现新子节点列表中的某个虚拟节点没有对应的旧子节点时，会创建一个用于记录旧子节点列表中每个带有 key 的虚拟节点对应的索引值的对象 
          oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        }
        //oldKeyToIdx:用于存储旧子节点列表中带有key的虚拟节点的索引值
        //isDef(newStartVnode.key)判断新子节点列表的起始节点是否存在key,如果存在key
        //oldKeyToIdx[newStartVnode.key]则根据获取当前新子节点列表在旧子节点列表字典对象的索引值
        //findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) 如果新子节点列表不存在key则,对于没有设置 key 属性的新子节点，
        //在旧子节点列表中进行线性搜索，找到与之相匹配的虚拟节点，从而确定其在旧子节点列表中的位置。
        idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        // idxInOld 存储新子节点列表中的虚拟节点在旧子节点列表中的索引值
        if (isUndef(idxInOld)) {
          // 创建新的DOM元素即在旧子节点列表中没有找到匹配的虚拟节点），说明是全新的虚拟节点，需要创建新的 DOM 元素
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        } else {
          // 如果 旧子节点列表中存在与新旧子节点列表存在相同的key
          vnodeToMove = oldCh[idxInOld] // 需要移动的虚拟节点
          if (sameVnode(vnodeToMove, newStartVnode)) { // 如果需要移动的虚拟节点与新子节点列表的起始节点一样 
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)// 进行patch
            oldCh[idxInOld] = undefined // 重置旧子节点列表中的虚拟节点
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
            // 如果不一样则创建
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
          }
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
    if (oldStartIdx > oldEndIdx) {
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
    } else if (newStartIdx > newEndIdx) {
      // 遍历的过程结束newStartIdx>newEndIdx 说明旧子节点列表存在多余的节点,则删除这些多余的节点
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
  }
  function findIdxInOld (node, oldCh, start, end) {
    for (let i = start; i < end; i++) {
      const c = oldCh[i]
      if (isDef(c) && sameVnode(node, c)) return i
    }
  }
  /**
   * 更新虚拟节点的函数
   * @param {*} oldVnode 旧的虚拟节点 
   * @param {*} vnode 要更新为新的虚拟节点
   * @param {*} insertedVnodeQueue 
   * @param {*} removeOnly 
   * @returns 
   */
  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    if (oldVnode == vnode) {
      return
    }
    // 1.确保新虚拟节点和旧虚拟节点共享同一个真实DOM节点
    const elm = vnode.elm = oldVnode.elm
    if (isTrue(oldVnode.isAsyncPlaceholder)) { // 是一个异步占位符节点
      if (isDef(vnode.asyncFactory.resolved)) { // 判断新虚拟节点是否已经被解析
        hydrate(oldVnode.elm, vnode, insertedVnodeQueue) // 进行混合
      } else {
        vnode.isAsyncPlaceholder = true
      }
      return
    }
    if (isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
      vnode.componentInstance = oldVnode.componentInstance
      return
    }
    let i
    const data = vnode.data
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
      i(oldVnode, vnode)
    }

    const oldCh = oldVnode.children
    const ch = vnode.children
    if (isDef(data) && isPatchable(vnode)) {
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
    }
    // 如果vnode没有文本节点
    if (isUndef(vnode.text)) {
      if (isDef(oldCh) && isDef(ch)) {
        //oldch与ch的children属性同时存在
        // 对子节点进行diff
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
        nodeOps.setTextContent(elm, '')
      }
    } else if (oldVnode.text !== vnode.text) {
      nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }
}